import { Task } from "../task";
import { ModernVersion } from "../version";
import { Downloader, Sha1 } from "../download";
import JSZip, { loadAsync } from "jszip";
import { readFile, writeFile } from "fs/promises";
import { relative, join as joinPath } from "path";
import { dirname } from "path";
import { Asset } from "../download";

class TaskDownloadLibraries extends Task {
    id = "download_libraries";
    dependOn = [];

    version: ModernVersion;

    constructor(version: ModernVersion) {
        super();
        this.version = version;
    }

    async run(): Promise<boolean> {
        const result = await new Downloader(32).add(this.version.libraries).download();
        return result.length === 0;
    }

    get progress(): null {
        return null;
    }
}

class TaskExtractNatives extends Task {
    id: string = "extract_natives";
    dependOn: string[] = ["download_libraries"];

    version: ModernVersion;

    constructor(version: ModernVersion) {
        super();
        this.version = version;
    }

    async extractNative(
        nativeJarLocation: string,
        nativesPath: string,
    ): Promise<void> {
        const zip: JSZip = await loadAsync(await readFile(nativeJarLocation));
        const promises: Promise<void>[] = [];

        for (const file in zip.files) {
            promises.push(
                (async () => {
                    if (!relative("META-INF/", file).startsWith("..")) return;
                    if (!file.endsWith(".dll")) return;

                    const nativePath = joinPath(nativesPath, file);
                    Downloader.createFolder(dirname(nativePath));
                    const data = await zip.files[file].async("nodebuffer");

                    if (Sha1.check(nativePath, Sha1._get(data))) return;
                    await writeFile(nativePath, data);
                })(),
            );
        }

        await Promise.all(promises);
    }

    async run(): Promise<boolean> {
        const nativesPath = joinPath(this.version.path, "natives");
        Downloader.createFolder(nativesPath);

        let result = true;
        const promises = [];

        for (let i = 0; i < this.version.natives.length; i++) {
            const native = this.version.natives[i];
            const nativeJarLocation = native.diskLocations[0];

            promises.push(
                (async () => {
                    try {
                        await this.extractNative(
                            nativeJarLocation,
                            nativesPath,
                        );
                    } catch(e) {
                        console.log("extract error", native, e);
                        result = false;
                    }
                })(),
            );
        }
        await Promise.all(promises);

        return result;
    }

    get progress(): number | null {
        return null;
    }
}

class TaskDownloadClientJar extends Task {
    id:string = "download_client_jar";
    dependOn:string[] = [];

    version:ModernVersion;

    constructor(version:ModernVersion) {
        super();
        this.version = version;
    }

    async run():Promise<boolean> {
        if (!this.version.clientJar) throw Error("this shouldn't happen. USE FACTORY TO CREATE VERSION!");
        try {
            await Downloader.download(this.version.clientJar);
            return true;
        }
        catch {
            return false;
        }
    }

    get progress(): number | null {
        return null;
    }
}

class TaskDownloadAssetIndex extends Task {
    id:string = "download_assetindex";
    dependOn:string[] = [];

    version:ModernVersion;

    constructor(version:ModernVersion) {
        super();
        this.version = version;
    }

    async run(): Promise<boolean> {
        if (!this.version.assetIndex) throw Error("this shouldn't happen. USE FACTORY TO CREATE VERSION!");
        try {
            await Downloader.download(this.version.assetIndex);
            return true;
        } catch(e) {
            return false;
        }
    }

    get progress(): number | null {
        return null;
    }
}

class TaskDownloadAssets extends Task {
    id:string = "download_assets";
    dependOn:string[] = ["download_assetindex"];

    version:ModernVersion;

    constructor(version:ModernVersion) {
        super();
        this.version = version;
    }

    async run(): Promise<boolean> {
        if (!this.version.assetIndex) throw Error("this shouldn't happen. USE FACTORY TO CREATE VERSION!");
        try {
            const assetIndexObject = require(this.version.assetIndex.diskLocations[0]);
            const assets:Asset[] = [];

            if (!assetIndexObject.objects) throw Error;
            for (let name in assetIndexObject.objects) {
                const obj = assetIndexObject.objects[name];
                if (!obj.hash) throw Error;
                assets.push(new Asset(name, obj.hash, this.version.assetIndex));
            }

            const results = await new Downloader(32).add(assets).download();
            if (results.length) throw Error();

            return true;
        } catch(e) { 
            console.log("download error", e)
            return false; 
        }
    }

    get progress(): number | null {
        return null;
    }
}

export {
    TaskDownloadAssetIndex,
    TaskDownloadAssets,
    TaskDownloadClientJar,
    TaskDownloadLibraries,
    TaskExtractNatives
}